﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ServiceModel.Channels;

namespace DotNETX.FileServer
{
    public class FileManagerClient : DotNETX.CommonWcfClient.CommonClient<IFileManagerService>
    {
        public FileManagerClient()
            : base()
        {
        }

        public FileManagerClient(string configName, string endpointAddress)
            : base(configName, endpointAddress)
        {

        }

        public FileManagerClient(Binding binding, string endpointAddress)
            : base(binding, new System.ServiceModel.EndpointAddress(endpointAddress))
        {
        }

        private string CreateRemoteFileHandler(string virtualPath, long length, bool cover)
        {
            string fhandler = null;
            try
            {
                fhandler = Proxy.PutFile(new RemoteFileInfo() { VirtualPath = virtualPath, Length = length }, cover);
            }
            catch (System.ServiceModel.FaultException<FileErrorCode> fe)
            {
                FileErrorCode fec = fe.Detail;
            }
            return fhandler;
        }

        public bool PutFile(string filePath, string virtualPath, bool cover = false, int bufferSize = 20480, int retry_inerrorcount = 10)
        {
            if (!File.Exists(filePath)) return false;
            FileInfo fi = new FileInfo(filePath);
            string fhandler = CreateRemoteFileHandler(virtualPath, fi.Length, cover);
            if (null == fhandler) return false;

            byte[] buffer = new byte[bufferSize];
            using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                long offset = 0;
                int count = 0;
                int errorcount = 0;
                var retry = new Func<bool>(() =>
                {
                    fs.Seek(-count, SeekOrigin.Current);
                    offset -= count;
                    errorcount++;
                    if (errorcount > retry_inerrorcount)
                    {
                        Proxy.CancelPutFile(fhandler);
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                });
                while ((count = fs.Read(buffer, 0, bufferSize)) > 0)
                {
                    try
                    {
                        FileSectionInfo fsi = new FileSectionInfo();
                        fsi.TotalLength = fi.Length;
                        fsi.Offset = offset;
                        offset += count;
                        fsi.ContentLength = count;
                        fsi.Content = buffer;
                        int uped = Proxy.PutFileSection(fhandler, fsi);
                        if (uped != fsi.ContentLength)
                        {
                            if (!retry()) return false;
                        }
                    }
                    catch (System.ServiceModel.FaultException fe)
                    {
                        if (!retry()) return false;
                    }
                    catch (Exception ex)
                    {
                        if (!retry()) return false;
                    }
                }
            }
            return true;
        }

        public bool PutFile2(string filePath, string virtualPath, bool cover = false, int bufferSize = 20480, int retry_inerrorcount = 10)
        {
            if (!File.Exists(filePath)) return false;
            FileInfo fi = new FileInfo(filePath);
            string fhandler = CreateRemoteFileHandler(virtualPath, fi.Length, cover);
            if (null == fhandler) return false;

            using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                long offset = 0;
                int count = 0;
                int errorcount = 0;

                Func<FileSectionInfo, bool> doAsyncPut = (fsi) =>
                {
                    try
                    {
                        int uped = Proxy.PutFileSection(fhandler, fsi);
                        if (uped != fsi.ContentLength)
                        {
                            return false;
                        }
                        return true;
                    }
                    catch
                    {
                        return false;
                    }
                };
                Func<byte[], int, int, int> doAsyncRead = (buf, os, bufsize) =>
                {
                    int c = fs.Read(buf, 0, bufsize);
                    return c;
                };
                byte[] buffer = new byte[bufferSize];
                byte[] ubuffer = new byte[bufferSize];
                byte[] cbuffer = null;

                count = doAsyncRead(buffer, 0, bufferSize);

                while (count > 0)
                {
                    cbuffer = buffer;
                    buffer = ubuffer;
                    ubuffer = cbuffer;
                    FileSectionInfo fsi = new FileSectionInfo();
                    fsi.TotalLength = fi.Length;
                    fsi.Offset = offset;
                    offset += count;
                    fsi.ContentLength = count;
                    fsi.Content = ubuffer;
                    IAsyncResult par = doAsyncPut.BeginInvoke(fsi, null, null);
                    IAsyncResult ar = doAsyncRead.BeginInvoke(buffer, 0, bufferSize, null, null);
                    while (!doAsyncPut.EndInvoke(par))
                    {
                        errorcount++;
                        if (errorcount > retry_inerrorcount)
                        {
                            Proxy.CancelPutFile(fhandler);
                            return false;
                        }
                        doAsyncPut.BeginInvoke(fsi, null, null);
                    }
                    count = doAsyncRead.EndInvoke(ar);
                }
            }
            return true;
        }

        public void PutFileAsync(Action<double> progress, Action<bool, Exception> complete, string filePath, string virtualPath, bool cover = false, int bufferSize = 20480, int retry_inerrorcount = 10)
        {
            System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.DoWork += (s, e) =>
            {
                if (!File.Exists(filePath))
                {
                    e.Result = false;
                    return;
                }
                FileInfo fi = new FileInfo(filePath);
                string fhandler = CreateRemoteFileHandler(virtualPath, fi.Length, cover);
                if (null == fhandler)
                {
                    e.Result = false;
                    return;
                }


                byte[] buffer = new byte[bufferSize];
                using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    long offset = 0;
                    int count = 0;
                    int errorcount = 0;
                    var retry = new Func<bool>(() =>
                    {
                        fs.Seek(-count, SeekOrigin.Current);
                        offset -= count;
                        errorcount++;
                        if (errorcount > retry_inerrorcount)
                        {
                            Proxy.CancelPutFile(fhandler);
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    });
                    while ((count = fs.Read(buffer, 0, bufferSize)) > 0)
                    {
                        try
                        {
                            FileSectionInfo fsi = new FileSectionInfo();
                            fsi.TotalLength = fi.Length;
                            fsi.Offset = offset;
                            offset += count;
                            fsi.ContentLength = count;
                            fsi.Content = buffer;
                            int uped = Proxy.PutFileSection(fhandler, fsi);
                            if (uped != fsi.ContentLength)
                            {
                                if (!retry())
                                {
                                    e.Result = false;
                                    return;
                                }
                            }
                        }
                        catch (System.ServiceModel.FaultException fe)
                        {
                            if (!retry())
                            {
                                e.Result = false;
                                return;
                            }
                        }
                        catch (Exception ex)
                        {
                            if (!retry())
                            {
                                e.Result = false;
                                return;
                            }
                        }
                        finally
                        {
                            double p = (double)offset / fi.Length;
                            bw.ReportProgress((int)(p * 100), p);
                        }
                    }
                }
                e.Result = true;
            };
            bw.RunWorkerCompleted += (s, e) =>
            {
                bool r = e.Result is bool ? (bool)e.Result : false;
                if (null != complete) complete(r, e.Error);
            };
            bw.ProgressChanged += (s, e) =>
            {
                double p = e.UserState is double ? (double)e.UserState : e.ProgressPercentage / 100.0;
                if (null != progress) progress(p);
            };
            bw.RunWorkerAsync();
        }

        public void PutFileAsync2(Action<double> progress, Action<bool, Exception> complete, string filePath, string virtualPath, bool cover = false, int bufferSize = 20480, int retry_inerrorcount = 10)
        {
            System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.DoWork += (s, e) =>
            {
                if (!File.Exists(filePath))
                {
                    e.Result = false;
                    return;
                }
                FileInfo fi = new FileInfo(filePath);
                string fhandler = CreateRemoteFileHandler(virtualPath, fi.Length, cover);
                if (null == fhandler)
                {
                    e.Result = false;
                    return;
                }

                using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    long offset = 0;
                    int count = 0;
                    int errorcount = 0;

                    Func<FileSectionInfo, bool> doAsyncPut = (fsi) =>
                    {
                        try
                        {
                            int uped = Proxy.PutFileSection(fhandler, fsi);
                            if (uped != fsi.ContentLength)
                            {
                                return false;
                            }
                            return true;
                        }
                        catch
                        {
                            return false;
                        }
                    };
                    Func<byte[], int, int, int> doAsyncRead = (buf, os, bufsize) =>
                    {
                        int c = fs.Read(buf, 0, bufsize);
                        return c;
                    };
                    byte[] buffer = new byte[bufferSize];
                    byte[] ubuffer = new byte[bufferSize];
                    byte[] cbuffer = null;

                    count = doAsyncRead(buffer, 0, bufferSize);

                    while (count > 0)
                    {
                        cbuffer = buffer;
                        buffer = ubuffer;
                        ubuffer = cbuffer;
                        FileSectionInfo fsi = new FileSectionInfo();
                        fsi.TotalLength = fi.Length;
                        fsi.Offset = offset;
                        offset += count;
                        fsi.ContentLength = count;
                        fsi.Content = ubuffer;
                        IAsyncResult par = doAsyncPut.BeginInvoke(fsi, null, null);
                        IAsyncResult ar = doAsyncRead.BeginInvoke(buffer, 0, bufferSize, null, null);
                        while (!doAsyncPut.EndInvoke(par))
                        {
                            errorcount++;
                            if (errorcount > retry_inerrorcount)
                            {
                                Proxy.CancelPutFile(fhandler);
                                e.Result = false;
                                return;
                            }
                            doAsyncPut.BeginInvoke(fsi, null, null);
                        }
                        double p = (double)offset / fi.Length;
                        bw.ReportProgress((int)(p * 100), p);
                        count = doAsyncRead.EndInvoke(ar);
                    }
                }

                e.Result = true;
            };
            bw.RunWorkerCompleted += (s, e) =>
            {
                bool r = e.Result is bool ? (bool)e.Result : false;
                if (null != complete) complete(r, e.Error);
            };
            bw.ProgressChanged += (s, e) =>
            {
                double p = e.UserState is double ? (double)e.UserState : e.ProgressPercentage / 100.0;
                if (null != progress) progress(p);
            };
            bw.RunWorkerAsync();
        }

        public bool GetFile(string virtualPath, string filePath, bool cover = false, int bufferSize = 20480, int retry_inerrorcount = 10)
        {
            if (File.Exists(filePath))
            {
                if (cover)
                {
                    File.Delete(filePath);
                }
                else
                {
                    return false;
                }
            }
            FileInfo fi = new FileInfo(filePath);
            if (!Directory.Exists(fi.DirectoryName)) Directory.CreateDirectory(fi.DirectoryName);

            RemoteFileInfo rfi = null;
            try
            {
                rfi = Proxy.GetFile(virtualPath);
            }
            catch (System.ServiceModel.FaultException<FileErrorCode> fe)
            {
                return false;
            }
            if (null == rfi) return false;

            byte[] buffer = new byte[bufferSize];
            using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
            {
                long offset = 0;
                int errorcount = 0;
                FileSectionInfo fsi = null;
                do
                {
                    try
                    {
                        fsi = Proxy.GetFileSection(virtualPath, offset, bufferSize);
                        if (fsi.ContentLength != bufferSize && fsi.ContentLength != (rfi.Length - offset))
                        {
                            errorcount++;
                            if (errorcount > retry_inerrorcount)
                            {
                                return false;
                            }
                            continue;
                        }
                        offset += fsi.ContentLength;
                    }
                    catch (System.ServiceModel.FaultException<FileErrorCode> fe)
                    {
                        errorcount++;
                        if (errorcount > retry_inerrorcount)
                        {
                            return false;
                        }
                        continue;
                    }
                    fs.Write(fsi.Content, 0, fsi.ContentLength);
                }
                while (offset < rfi.Length);
                fs.Flush(true);
            }
            return true;
        }

        public void GetFileAsync(Action<double> progress, Action<bool, Exception> complete, string virtualPath, string filePath, bool cover = false, int bufferSize = 20480, int retry_inerrorcount = 10)
        {
            System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.DoWork += (s, e) =>
            {
                if (File.Exists(filePath))
                {
                    if (cover)
                    {
                        File.Delete(filePath);
                    }
                    else
                    {
                        e.Result = false;
                        return;
                    }
                }
                FileInfo fi = new FileInfo(filePath);
                if (!Directory.Exists(fi.DirectoryName)) Directory.CreateDirectory(fi.DirectoryName);

                RemoteFileInfo rfi = null;
                try
                {
                    rfi = Proxy.GetFile(virtualPath);
                }
                catch (System.ServiceModel.FaultException<FileErrorCode> fe)
                {
                    e.Result = false;
                    return;
                }
                if (null == rfi)
                {
                    e.Result = false;
                    return;
                }

                byte[] buffer = new byte[bufferSize];
                using (System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
                {
                    long offset = 0;
                    int errorcount = 0;
                    FileSectionInfo fsi = null;
                    do
                    {
                        try
                        {
                            fsi = Proxy.GetFileSection(virtualPath, offset, bufferSize);
                            if (fsi.ContentLength != bufferSize && fsi.ContentLength != (rfi.Length - offset))
                            {
                                errorcount++;
                                if (errorcount > retry_inerrorcount)
                                {
                                    e.Result = false;
                                    return;
                                }
                                continue;
                            }
                            offset += fsi.ContentLength;
                        }
                        catch (System.ServiceModel.FaultException<FileErrorCode> fe)
                        {
                            errorcount++;
                            if (errorcount > retry_inerrorcount)
                            {

                                e.Result = false;
                                return;
                            }
                            continue;
                        }
                        fs.Write(fsi.Content, 0, fsi.ContentLength);
                        double p = (double)offset / rfi.Length;
                        bw.ReportProgress((int)(p * 100), p);
                    }
                    while (offset < rfi.Length);
                    fs.Flush(true);
                }
                e.Result = true;
            };
            bw.RunWorkerCompleted += (s, e) =>
            {
                bool r = e.Result is bool ? (bool)e.Result : false;
                if (null != complete) complete(r, e.Error);
            };
            bw.ProgressChanged += (s, e) =>
            {
                double p = e.UserState is double ? (double)e.UserState : e.ProgressPercentage / 100.0;
                if (null != progress) progress(p);
            };
            bw.RunWorkerAsync();
        }

        public static FileManagerClient Create(string ip, int port, string protocal = "net.tcp", string catalog = "DotNETX.FileServer.FileManagerService", string configName = "DotNETX.FileServer.FileManagerService")
        {
            return new FileManagerClient(configName, string.Format("{2}://{0}:{1}/{3}", ip, port, protocal, catalog));
        }
    }
}
